#include "pch.h"

/*
   AngelCode Scripting Library
   Copyright (c) 2003-2018 Andreas Jonsson

   This software is provided 'as-is', without any express or implied
   warranty. In no event will the authors be held liable for any
   damages arising from the use of this software.

   Permission is granted to anyone to use this software for any
   purpose, including commercial applications, and to alter it and
   redistribute it freely, subject to the following restrictions:

   1. The origin of this software must not be misrepresented; you
	  must not claim that you wrote the original software. If you use
	  this software in a product, an acknowledgment in the product
	  documentation would be appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and
	  must not be misrepresented as being the original software.

   3. This notice may not be removed or altered from any source
	  distribution.

   The original version of this library can be located at:
   http://www.angelcode.com/angelscript/

   Andreas Jonsson
   andreas@angelcode.com
*/

// With some compile level optimizations the functions don't clear the FPU
// stack themselves. So we have to do it as part of calling the native functions,
// as the compiler will not be able to predict when it is supposed to do it by
// itself due to the dynamic nature of scripts
//
// - fninit clears the FPU stack and the FPU control word
// - emms only clears the FPU stack, while preserving the FPU control word
//
// By default I use fninit as it seems to be what works for most people,
// but some may find it necessary to define this as emms instead.
//
// TODO: Figure out when one or the other must be used, and a way to
//       configure this automatically in as_config.h
//
#ifndef CLEAR_FPU_STACK
#define CLEAR_FPU_STACK fninit
#endif

// These macros are just to allow me to use the above macro in the GNUC style inline assembly
#define _S(x) _TOSTRING(x)
#define _TOSTRING(x) #x


#define asQWORD uint64_t
#define asDWORD uint32_t
#define asFUNCTION_t void*
// Prototypes
extern "C"
{
	asQWORD CallCDeclFunction(const asDWORD* args, int paramSize, asFUNCTION_t func);
	asQWORD CallCDeclFunctionObjLast(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func);
	asQWORD CallCDeclFunctionObjFirst(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func);
	asQWORD CallCDeclFunctionRetByRef(const asDWORD* args, int paramSize, asFUNCTION_t func, void* retPtr);
	asQWORD CallCDeclFunctionRetByRefObjLast(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func, void* retPtr);
	asQWORD CallCDeclFunctionRetByRefObjFirst(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func, void* retPtr);
	asQWORD CallSTDCallFunction(const asDWORD* args, int paramSize, asFUNCTION_t func);
	asQWORD CallThisCallFunction(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func);
	asQWORD CallThisCallFunctionRetByRef(const void*, const asDWORD*, int, asFUNCTION_t, void* retPtr);

	asDWORD GetReturnedFloat();
	asQWORD GetReturnedDouble();
}
// On GCC we need to prevent the compiler from inlining these assembler routines when
// optimizing for speed (-O3), as the loop labels get duplicated which cause compile errors.

#define NOINLINE


asQWORD NOINLINE CallCDeclFunction(const asDWORD* args, int paramSize, asFUNCTION_t func)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

		// Call function
		call[func]

			// Pop arguments from stack
			add  esp, paramSize

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}

	return retQW;
}

asQWORD NOINLINE CallCDeclFunctionObjLast(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Push the object pointer as the last argument to the function
		push obj

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

		// Call function
		call[func]

			// Pop arguments from stack
			add  esp, paramSize
			add  esp, 4

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}

	return retQW;
}

asQWORD NOINLINE CallCDeclFunctionObjFirst(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

		// push object as first parameter
		push obj

			// Call function
			call[func]

			// Pop arguments from stack
			add  esp, paramSize
			add  esp, 4

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}


	return retQW;
}

asQWORD NOINLINE CallCDeclFunctionRetByRefObjFirst(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func, void* retPtr)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

		// Push the object pointer
		push obj

			// Push the return pointer
			push retPtr;

		// Call function
		call[func]

			// Pop arguments from stack
			add  esp, paramSize

#ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER
			// Pop the return pointer
			add  esp, 8
#else
			add  esp, 4
#endif

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}

	return retQW;
}

asQWORD NOINLINE CallCDeclFunctionRetByRef(const asDWORD* args, int paramSize, asFUNCTION_t func, void* retPtr)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

		// Push the return pointer
		push retPtr;

		// Call function
		call[func]

			// Pop arguments from stack
			add  esp, paramSize

#ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER
			// Pop the return pointer
			add  esp, 4
#endif

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx

			// return value in EAX or EAX:EDX
	}


	return retQW;
}

asQWORD NOINLINE CallCDeclFunctionRetByRefObjLast(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func, void* retPtr)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		push obj

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

		// Push the return pointer
		push retPtr;

		// Call function
		call[func]

			// Pop arguments from stack
			add  esp, paramSize
			add  esp, 4

#ifndef CALLEE_POPS_HIDDEN_RETURN_POINTER
			// Pop the return pointer
			add  esp, 4
#endif

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}


	return retQW;
}

asQWORD NOINLINE CallSTDCallFunction(const asDWORD* args, int paramSize, asFUNCTION_t func)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

		// Call function
		call[func]

			// The callee already removed parameters from the stack

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}


	return retQW;
}


asQWORD NOINLINE CallThisCallFunction(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

#ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK
		// Push the object pointer on the stack
		push obj
#else
		// Move object pointer to ECX
		mov  ecx, obj
#endif

			// Call function
			call[func]

#ifndef THISCALL_CALLEE_POPS_ARGUMENTS
			// Pop arguments
			add  esp, paramSize
#ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK
			// Pop object pointer
			add esp, 4
#endif
#endif

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}

	return retQW;
}

asQWORD NOINLINE CallThisCallFunctionRetByRef(const void* obj, const asDWORD* args, int paramSize, asFUNCTION_t func, void* retPtr)
{
	volatile asQWORD retQW = 0;


	// Copy the data to the real stack. If we fail to do
	// this we may run into trouble in case of exceptions.
	__asm
	{
		// We must save registers that are used
		push ecx

		// Clear the FPU stack, in case the called function doesn't do it by itself
		CLEAR_FPU_STACK

		// Copy arguments from script
		// stack to application stack
		mov  ecx, paramSize
		mov  eax, args
		add  eax, ecx
		cmp  ecx, 0
		je   endcopy
		copyloop :
		sub  eax, 4
			push dword ptr[eax]
			sub  ecx, 4
			jne  copyloop
			endcopy :

#ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK
		// Push the object pointer on the stack
		push obj
#else
		// Move object pointer to ECX
		mov  ecx, obj
#endif

			// Push the return pointer
			push retPtr

			// Call function
			call[func]

#ifndef THISCALL_CALLEE_POPS_HIDDEN_RETURN_POINTER
			// Pop the return pointer
			add  esp, 4
#endif

#ifndef THISCALL_CALLEE_POPS_ARGUMENTS
			// Pop arguments
			add  esp, paramSize
#ifdef THISCALL_PASS_OBJECT_POINTER_ON_THE_STACK
			// Pop object pointer
			add esp, 4
#endif
#endif

			// Copy return value from EAX:EDX
			lea  ecx, retQW
			mov[ecx], eax
			mov  4[ecx], edx

			// Restore registers
			pop  ecx
	}


	return retQW;
}

asDWORD GetReturnedFloat()
{
	asDWORD f;


	// Get the float value from ST0
	__asm fstp dword ptr[f]

	return f;
}

asQWORD GetReturnedDouble()
{
	asQWORD d;


	// Get the double value from ST0
	__asm fstp qword ptr[d]


	return d;
}




